%{
#include <stdio.h>
#include "util.h"
#include "symbol.h"
#include "errormsg.h"
#include "absyn.h"

extern int yylex();
A_exp absyn_root;

void yyerror(char *s)
{
    EM_error(EM_tokPos, "%s", s);
}
%}

%union
{
    int        pos;
    int         ival;
    string        sval;
    A_var        var;
    A_exp        exp;
    A_expList     exps;
    S_symbol     symbol;
    A_fieldList fieldList;
    A_dec        dec;
    A_decList    decList;
    A_asmList    asmList;
    A_subList    subList;
}

%token <sval> ID STRING
%token <ival> INT

%token
COMMA COLON SEMICOLON LPAREN RPAREN LBRACK RBRACK
LBRACE RBRACE DOT
PLUS MINUS TIMES DIVIDE EQ NEQ LT LE GT GE
AND OR ASSIGN
IF ELSE WHILE FOR TO DO OF
BREAK NIL
ASM ASM_CODE ASM_NL
TYPEDEF STRUCT SELFADD LSHIFT RSHIFT ARSHIFT XOR

%left MINUS PLUS
%left TIMES DIVIDE
%left LT LE GT GE EQ NEQ

%start program

%type <exp> exp exps program ifexp 
%type <exp> dec call whilexp forexp
%type <exp> cal number rvalue
%type <var> lvalue _lvalue array 
%type <subList> sub _sub
%type <exps> _exps params _params
%type <symbol> symbol
%type <fieldList> fields _fields paramsDec _paramsDec
%type <exp> fun_dec var_dec type_dec assign
%type <dec> var fun type
%type <asmList> _asmm asmm
%type <sval> asm_code
%type <subList> _subDec subDec

%nonassoc LOWER_THAN_ELSE1
%nonassoc LOWER_THAN_ELSE2
%nonassoc ELSE
%%
program: exps    { absyn_root = $1; }

symbol: ID     { $$ = S_Symbol(yyval.sval); }

number: INT    { $$ = A_IntExp(EM_tokPos, yyval.ival); }

exps:    exp _exps    { $$ = A_SeqExp(EM_tokPos,A_ExpList($1,$2)); }

_exps:    SEMICOLON exp _exps    { $$ = A_ExpList($2,$3);}
    | SEMICOLON        { $$ = NULL; }

exp:     assign            { $$ = $1; }
    | ifexp            { $$ = $1; }
    | whilexp        { $$ = $1; }
    | forexp        { $$ = $1; }
    | call            { $$ = $1; }
    | dec            { $$ = $1; }
    | cal            { $$ = $1; }
    | LPAREN exps RPAREN    { $$ = $2; }
    | ASM LPAREN asmm RPAREN    { $$ = A_AsmExp(EM_tokPos, $3);}
    | BREAK            { $$ = A_BreakExp(); }
    | STRING { $$ = A_StringExp(EM_tokPos, yyval.sval); }
                    
assign:  lvalue ASSIGN rvalue
        { $$ = A_AssignExp(EM_tokPos, $1, $3);}
        
rvalue: cal
    | call
    |         { $$ = NULL; }

asm_code: ASM_CODE    { $$ = String(yyval.sval) }

asmm:    asm_code _asmm        { $$ = A_AsmList($1,$2); }

_asmm:     ASM_NL    asm_code _asmm    { $$ = A_AsmList($2,$3); }
    | ASM_NL        { $$ = NULL; }

lvalue: array _lvalue
        {
            if ($2 == NULL)
                $$ = $1;
            else
                $$ = A_FieldVar(EM_tokPos, $1, $2);
        }
    | symbol _lvalue
        {
            if ($2 == NULL)
                $$ = A_SimpleVar(EM_tokPos, $1); 
            else
                $$ = A_FieldVar(EM_tokPos,  
                    A_SimpleVar(EM_tokPos, $1) , $2);
        }

_lvalue: DOT lvalue         { $$ = $2; }
    |            { $$ = NULL; }

cal:       number
       | lvalue         { $$ = A_VarExp(EM_tokPos, $1);}
    | cal PLUS cal         { $$ = A_OpExp(EM_tokPos, A_plusOp, $1, $3);}
    | cal MINUS cal     { $$ = A_OpExp(EM_tokPos, A_minusOp, $1, $3); }
    | cal DIVIDE cal     { $$ = A_OpExp(EM_tokPos, A_divideOp, $1, $3); }
    | cal TIMES cal     { $$ = A_OpExp(EM_tokPos, A_timesOp, $1, $3); }
    | cal LT cal         { $$ = A_OpExp(EM_tokPos, A_ltOp, $1, $3); }
    | cal LE cal         { $$ = A_OpExp(EM_tokPos, A_leOp, $1, $3); }
    | cal GT cal         { $$ = A_OpExp(EM_tokPos, A_gtOp, $1, $3); }
    | cal GE cal         { $$ = A_OpExp(EM_tokPos, A_geOp, $1, $3); }
    | cal EQ cal         { $$ = A_OpExp(EM_tokPos, A_eqOp, $1, $3); }
    | cal NEQ cal         { $$ = A_OpExp(EM_tokPos, A_neqOp, $1, $3); }
    | LBRACK cal RBRACK     { $$ = $2; }

array:    symbol sub    { $$ = A_SubscriptVar(EM_tokPos, $1, $2); }

sub:    LBRACE exp RBRACE _sub    { $$ = A_SubList($2, $4); } 

_sub:    LBRACE exp RBRACE _sub    { $$ = A_SubList($2, $4); }
    |            { $$ = NULL; }

ifexp:  IF LBRACK exp RBRACK exp %prec LOWER_THAN_ELSE1
            { $$ = A_IfExp(EM_tokPos, $3, $5, NULL);}
    | IF LBRACK exp RBRACK exp ELSE exp
            { $$ = A_IfExp(EM_tokPos, $3, $5, $7);}

whilexp: WHILE LBRACK exp RBRACK exp
            { $$ = A_WhileExp(EM_tokPos, $3, $5);}

forexp: FOR LBRACK exp SEMICOLON exp SEMICOLON exp RBRACK exp
            { $$ = A_ForExp(EM_tokPos, $3, $5, $7, $9, FALSE);}

dec:    fun_dec        { $$ = $1; }
    | var_dec    { $$ = $1; }
    | type_dec

fun_dec: fun         { $$ = A_DecExp(EM_tokPos, $1); }

fun:    symbol symbol LBRACK paramsDec RBRACK exp
            { $$ = A_FunDec(EM_tokPos, $2, $4, $1, $6); }
    | symbol symbol LBRACK RBRACK LPAREN exps RPAREN
            { $$ = A_FunDec(EM_tokPos, $2, NULL, $1, $6);}

var_dec: var         { $$ = A_DecExp(EM_tokPos, $1);}

var:    symbol symbol      { $$ = A_VarDec(EM_tokPos, $2, $1, NULL); }
    | symbol symbol ASSIGN exp { $$ = A_VarDec(EM_tokPos, $2, $1, $4); }

type_dec: type        { $$ = A_DecExp(EM_tokPos, $1);}

type:    TYPEDEF symbol symbol
            { $$ = A_TypeDec(EM_tokPos, A_NameTy(EM_tokPos, $2, $3));}
    | STRUCT symbol LPAREN fields RPAREN
            { $$ = A_TypeDec(EM_tokPos, A_RecordTy(EM_tokPos, $2, $4)); }
    | TYPEDEF symbol subDec symbol 
            { $$ = A_TypeDec(EM_tokPos, A_ArrayTy(EM_tokPos, $4, $2, $3));}
            
subDec: LBRACE number RBRACE _subDec
            { $$ = A_SubList($2, $4);}
_subDec: LBRACE number RBRACE _subDec    
            { $$ = A_SubList($2, $4);}
    |        { $$ = NULL; }

params: exp _params    { $$ = A_ExpList($1,$2); }

_params: COMMA exp _params { $$ = A_ExpList($2, $3); }
    |         { $$ = NULL; }
    
paramsDec: symbol symbol _paramsDec
            { $$ = A_FieldList( A_Field(EM_tokPos, $1, $2, FALSE),$3); }

_paramsDec: SEMICOLON paramsDec    { $$ = $2; }
    |        { $$ = NULL; }

fields: symbol symbol _fields
            { $$ = A_FieldList( A_Field(EM_tokPos, $2, $1, FALSE),$3); }

_fields: SEMICOLON fields { $$ = $2; }
    | SEMICOLON    { $$ = NULL; }
    
call:    symbol LBRACK RBRACK
            { $$ = A_CallExp(EM_tokPos, $1, NULL); }
    | symbol LBRACK params RBRACK
            { $$ = A_CallExp(EM_tokPos, $1, $3); }
